package com.pactera.jep.service.sys.service.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import com.pactera.jep.core.exception.ServiceException;
import com.pactera.jep.service.sys.conf.properties.FtpProperties;
import com.pactera.jep.service.sys.service.FTPService;
import com.pactera.jep.service.sys.util.MD5Encoder;

/**
 * {@link FTPService}实现类
 * 
 * @author ghost
 *
 */
@Service
@ConditionalOnProperty(name="save.type", havingValue="FTP", matchIfMissing=false)
public class FTPServiceImpl implements FTPService {

	private final static Logger logger = LoggerFactory.getLogger(FTPServiceImpl.class);

	@Autowired
	private FtpProperties ftpProperties;

	@Override
	public String save(String localFile) throws ServiceException {
		Assert.hasText(localFile, "给定的本地文件名不能为空！");
		return doSave(null, localFile);
	}
	
	@Override
	public String save(String targetPath, String localFile) throws ServiceException {
		Assert.hasText(localFile, "给定的本地文件名不能为空！");
		if(!StringUtils.isBlank(targetPath) && targetPath.startsWith("/")) {
			throw new ServiceException("远程目录不能以\"/\"开头");
		}
		return doSave(targetPath, localFile);
	}
	
	@Override
	public boolean save(String targetPath, String targetFile, InputStream in) throws ServiceException {
		Assert.hasText(targetFile, "远程文件名不能为空！");
		Assert.notNull(in, "上传的文件流不能为空！");
		FTPClient ftpClient = null;
		try {
			ftpClient = getFTPClient();
			// 如果目录不存在，那么创建这个目录
			if (StringUtils.isNotBlank(targetPath)) {
				String workDir = ftpProperties.getWorkDir();
				workDir = StringUtils.isBlank(workDir)?"~":("~/" + workDir);
				String path = workDir + "/" + targetPath;
				createDirIfNotExist(ftpClient, targetPath);
				if (!ftpClient.changeWorkingDirectory(targetPath)) {
					logger.error("切换至{}目录失败！", path);
					throw new ServiceException("切换至" + path + "失败！");
				}
			}
			// 然后上传文件
			return ftpClient.storeUniqueFile(targetFile, in);
		} catch (IOException e) {
			logger.error("上传文件至FTP服务器出现异常：{}", e.getMessage(), e);
			throw new ServiceException("上传文件出现异常" + e.getMessage(), e.getCause());
		} finally {
			closeFtpClientQuietly(ftpClient);
		}
	}

	private final String doSave(String targetPath, String localFile) throws ServiceException {
		int index = localFile.lastIndexOf(File.separator);
		String fileName = index > 0 ? localFile.substring(index + 1) : localFile;
		// 获取一个uuid来做盐进行md5加密文件名称(包括文件类型)
		UUID uuid = UUID.randomUUID();
		// 默认第一个小数点后都是文件类型
		index = localFile.indexOf(".");
		String suffix = index > 0 ? localFile.substring(index) : "";
		// 实际迭代次数为0
		String remoteFile = MD5Encoder.encode(fileName, uuid.toString(), 1).toUpperCase();
		remoteFile += suffix;
		FTPClient ftpClient = null;
		try (InputStream in = new FileInputStream(new File(localFile))) {
			ftpClient = getFTPClient();
			if (StringUtils.isNotBlank(targetPath)) {
				String workDir = ftpProperties.getWorkDir();
				workDir = StringUtils.isBlank(workDir)?"~":("~/" + workDir);
				String path = workDir + "/" + targetPath;
				// 如果待上传的目录不存在，那么在workDir中创建此目录
				createDirIfNotExist(ftpClient, targetPath);
				if (!ftpClient.changeWorkingDirectory(path)) {
					logger.error("切换至{}目录失败！", path);
					throw new ServiceException("切换至" + path + "失败！");
				}
			}
			boolean result = ftpClient.storeUniqueFile(remoteFile, in);
			return result ? remoteFile : null;
		} catch (IOException e) {
			logger.error("上传文件{}至FTP服务器出现异常：{}", localFile, e.getMessage(), e);
			throw new ServiceException("上传文件" + localFile + "出现异常" + e.getMessage(), e.getCause());
		} finally {
			closeFtpClientQuietly(ftpClient);
		}
	}

	@Override
	public boolean download(String remoteFile, String localPath) throws ServiceException {
		Assert.hasLength(remoteFile, "远程文件名不能为空！");
		Assert.hasLength(localPath, "本地文件名不能为空！");
		return doDownload(null, remoteFile, localPath);
	}

	@Override
	public boolean download(String remotePath, String remoteFile, String localPath) throws ServiceException {
		Assert.hasLength(remoteFile, "远程文件名不能为空！");
		Assert.hasLength(localPath, "本地文件名不能为空！");
		if(!StringUtils.isBlank(remotePath) && remotePath.startsWith("/")) {
			throw new ServiceException("远程目录不能以\"/\"开头");
		}
		return doDownload(remotePath, remoteFile, localPath);
	}
	
	private final boolean doDownload(String remotePath, String remoteFile, String localPath) throws ServiceException {
		FTPClient ftpClient = null;
		try (OutputStream out = new FileOutputStream(new File(localPath))) {
			ftpClient = getFTPClient();
			if (StringUtils.isNotBlank(remotePath)) {
				String workDir = ftpProperties.getWorkDir();
				workDir = StringUtils.isBlank(workDir)?"~":("~/" + workDir);
				String path = workDir + "/" + remotePath;
				if (!ftpClient.changeWorkingDirectory(path)) {
					logger.error("切换至{}目录失败！", path);
					throw new ServiceException("切换至" + remotePath + "目录失败！");
				}
			}
			List<String> fileNames = Arrays.asList(ftpClient.listNames());
			Optional<String> file = fileNames.stream().filter(item -> {
				if (StringUtils.isBlank(item)) {
					return false;
				}
				int index = item.lastIndexOf("/");
				if (index > 0) {
					item = item.substring(index + 1);
				}
				return item.equals(remoteFile);
			}).findAny();
			if (!file.isPresent()) {
				throw new ServiceException(remotePath + "中不存在文件" +  remoteFile);
			}
			return ftpClient.retrieveFile(remoteFile, out);
		} catch (IOException e) {
			logger.error("下载文件{}至本地目录过程中出现异常：{}", remoteFile, e.getMessage(), e);
			throw new ServiceException("文件下载过程中出现异常" + e.getMessage(), e.getCause());
		} finally {
			closeFtpClientQuietly(ftpClient);
		}
	}

	@Override
	public boolean remove(String remoteFile) throws ServiceException {
		Assert.hasLength(remoteFile, "远程文件名不能为空！");
		return doRemove(null, remoteFile);
	}

	@Override
	public boolean remove(String remotePath, String remoteFile) throws ServiceException {
		Assert.hasLength(remoteFile, "远程文件名不能为空！");
		if (remotePath.startsWith("/")) {
			throw new ServiceException("目标路径不能以\"/\"开头！");
		}
		return doRemove(remotePath, remoteFile);
	}
	
	private final boolean doRemove(String remotePath, String remoteFile) throws ServiceException {
		FTPClient ftpClient = getFTPClient();
		try {
			if (StringUtils.isNotBlank(remotePath)) {
				String workDir = ftpProperties.getWorkDir();
				workDir = StringUtils.isBlank(workDir)?"~":("~/" + workDir);
				String path = workDir + "/" + remotePath;
				if (!ftpClient.changeWorkingDirectory(path)) {
					logger.error("切换至{}目录失败！", path);
					throw new ServiceException("切换至" + remotePath + "目录失败！");
				}
			}
			return ftpClient.deleteFile(remoteFile);
		} catch (IOException e) {
			logger.error("删除文件{}过程中出现异常：{}", remoteFile, e.getMessage(), e);
			throw new ServiceException("删除文件" + remoteFile + "出现异常" + e.getMessage(), e.getCause());
		} finally {
			closeFtpClientQuietly(ftpClient);
		}
	}

	@Override
	public boolean fileExists(String fileName, String targetPath) throws ServiceException {
		Assert.hasLength(fileName, "远程文件名不能为空！");
		FTPClient ftpClient = null;
		try {
			ftpClient = getFTPClient();
			if (StringUtils.isNotBlank(targetPath)) {
				if (targetPath.startsWith("/")) {
					throw new ServiceException("目标路径不能以\"/\"开头！");
				} else {
					// 首先切换至根目录
					Optional<String> name = Arrays.asList(ftpClient.listNames("~/" + targetPath)).stream()
							.filter(item -> {
								if (StringUtils.isBlank(item)) {
									return false;
								}
								int index = item.lastIndexOf("/");
								if (index > 0) {
									item = item.substring(index + 1);
								}
								return item.equals(fileName);
							}).findAny();
					return name.isPresent();
				}
			}
		} catch (Exception e) {
			logger.error("判断文件{}在目录{}中是否存在时出现异常：{}", fileName,
					StringUtils.isBlank(targetPath) ? ftpProperties.getWorkDir() : targetPath, e.getMessage(), e);
			throw new ServiceException("处理过程出现异常：" + e.getMessage(), e.getCause());
		}
		return false;
	}

	/**
	 * 获得一个{@link FTPClient}对象实例
	 * 
	 * @return
	 */
	private FTPClient getFTPClient() throws ServiceException {
		FTPClient ftpClient = new FTPClient();
		if (ftpProperties == null) {
			throw new NullPointerException("没有配置ftp相关属性");
		} else {
			String host = ftpProperties.getHost();
			String username = ftpProperties.getUsername();
			String password = ftpProperties.getPassword();
			String workDir = ftpProperties.getWorkDir();
			Assert.hasLength(host, "ftp地址不能为空！");
			Assert.hasLength(username, "ftp用户名不能为空！");
			Assert.hasLength(password, "ftp密码不能为空！");
			try {
				// 连接服务器
				ftpClient.connect(host, ftpProperties.getPort());
				ftpClient.login(username, password);
				// 判断服务器是否支持指定的字符集
				String encoding = ftpProperties.getEncoding();
				// ftp服务器支持UTF8但是不支持UTF-8所以，要对编码进行处理
				String tmpEncoding = encoding.replaceAll("-", "");
				// 文件传输类型设置成BINARY，否则传输的时候会出现问题
				ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
				// 设置交互模式
				ftpClient.enterLocalPassiveMode();
				if (FTPReply.isPositiveCompletion(ftpClient.sendCommand("OPTS " + tmpEncoding, "ON"))) {
					ftpClient.setControlEncoding(tmpEncoding);
				} else {
					logger.info("FTP服务器{}不支持{}编码！", host, tmpEncoding);
					throw new ServiceException("FTP服务器不支持" + tmpEncoding + "编码.");
				}
				if (StringUtils.isNotBlank(workDir)) {
					// 尝试切换至workDir
					if (!ftpClient.changeWorkingDirectory(workDir)) {
						throw new ServiceException("切换至目录" + workDir + "失败，请确保FTP服务器中存在此目录！");
					}
				}
				return ftpClient;
			} catch (IOException e) {
				throw new ServiceException("创建FTPClient实例对象失败。", e.getCause());
			}
		}
	}

	private void closeFtpClientQuietly(FTPClient ftpClient) {
		if (ftpClient != null && ftpClient.isConnected()) {
			try {
				ftpClient.disconnect();
			} catch (IOException e) {
				// 关闭连接出现的异常就先不处理
			}
		}
	}
	
	/**
	 * 如果路径不存在那么创建路径，需要注意的是目录创建在当前目录下的
	 * @param ftpClient
	 * @param remotePath
	 * @return
	 * @throws IOException 
	 */
	private void createDirIfNotExist(FTPClient ftpClient, String remotePath) throws IOException, ServiceException {
		// 首先切分
		String[] paths = remotePath.split("/");
		String workDir = ftpProperties.getWorkDir();
		// workDir是一定要存在的，否则获取FTPClient对象的时候就会报错
		workDir = StringUtils.isBlank(workDir)?"~":("~/" + workDir);
		String target = workDir;
		for (String path : paths) {
			// 首先获取target下面的路径
			FTPFile[] files = ftpClient.listDirectories(target);
			target += ("/" + path);
			FTPFile file = null;
			for (FTPFile f : files) {
				if (f.getName().equals(path)) {
					file = f;
					break;
				}
			}
			// file为空，说明没有，创建之
			if (file == null) {
				boolean result = ftpClient.makeDirectory(target);
				if (!result) {
					throw new ServiceException("目录" + target + "创建失败！");
				}
			}
		}
		logger.info("目录{}创建成功！", target);
	}

	@Override
	public void copy(String remotePath, String remoteFile, String newFile) throws ServiceException {
		Assert.hasText(remoteFile, "指定的远程文件名不能为空！");
		Assert.hasText(newFile, "指定的远程新文件名不能为空！");
		FTPClient ftpClient = null;
		File tmpFile = null;
		try {
			ftpClient = getFTPClient();
			String workDir = ftpProperties.getWorkDir();
			if (StringUtils.isBlank(workDir)) {
				workDir = "~";
			} else {
				workDir = "~/" + workDir;
				// 如果workDir不是以"/"结尾，那么添加上"/"
				if (workDir.endsWith("/")) {
					workDir.substring(0, workDir.length() - 1);
				}
			}
			if (StringUtils.isNotBlank(remotePath)) {
				workDir += ("/" + remotePath);
			}
			// 首先进行工作目录的切换
			if (ftpClient.changeWorkingDirectory(workDir)) {
				// 切换之后首先将远程文件下载到本地临时文件中
				tmpFile = new File("/tmp/" + remoteFile);
				try (OutputStream out = new FileOutputStream(tmpFile)){
					boolean result = ftpClient.retrieveFile(remoteFile, out);
					if (!result) {
						logger.error("文件{}下载失败！", remoteFile);
						throw new ServiceException("文件复制失败！");
					}
				} 
				// 下载完成之后将文件上传
				try (InputStream in = new FileInputStream(tmpFile)){
					// 下载完成之后将文件上传
					boolean result = ftpClient.storeUniqueFile(newFile, in);
					if (!result) {
						logger.error("文件{}上传失败！", tmpFile.getAbsolutePath());
						throw new ServiceException("文件复制失败！");
					}
				}
			} else {
				logger.error("切换工作目录至{}失败！", workDir);
				throw new ServiceException("切换工作目录失败！");
			}
		} catch (IOException e) {
			logger.error("文件复制过程中出现异常：{}", e.getMessage(), e);
			throw new ServiceException("复制文件出现异常：" + e.getMessage(), e);
		} finally {
			closeFtpClientQuietly(ftpClient);
			if (tmpFile != null && tmpFile.exists()) {
				tmpFile.delete();
			}
		}
	}

	@Override
	public Map<InputStream, FTPClient> getAsStream(String remotePath, String remoteFile) throws ServiceException {
		Assert.hasText("remoteFile", "远程文件名不能为空！");
		FTPClient ftpClient = null; 
		Map<InputStream, FTPClient> result = new HashMap<>();
		try {
			ftpClient = getFTPClient();
			if (StringUtils.isNotBlank(remotePath)) {
				String workDir = ftpProperties.getWorkDir();
				workDir = StringUtils.isBlank(workDir)?"~":("~/" + workDir);
				String path = workDir + "/" + remotePath;
				if (!ftpClient.changeWorkingDirectory(path)) {
					logger.error("切换至{}目录失败！", path);
					throw new ServiceException("切换至" + remotePath + "目录失败！");
				}
			}
			List<String> fileNames = Arrays.asList(ftpClient.listNames());
			Optional<String> file = fileNames.stream().filter(item -> {
				if (StringUtils.isBlank(item)) {
					return false;
				}
				int index = item.lastIndexOf("/");
				if (index > 0) {
					item = item.substring(index + 1);
				}
				return item.equals(remoteFile);
			}).findAny();
			if (!file.isPresent()) {
				throw new ServiceException(remotePath + "中不存在文件" +  remoteFile);
			}
			InputStream in = ftpClient.retrieveFileStream(remoteFile);
			if (in == null) {
				throw new IOException("获取FTP文件的输入流失败！");
			}
			result.put(in, ftpClient);
			return result;
//			return ftpClient.retrieveFileStream(remoteFile);
		} catch (IOException e) {
			logger.error("获取文件过程中出现异常：{}", e.getMessage(), e);
			throw new ServiceException("获取文件过程中出现异常：" + e.getMessage(), e.getCause());
		} 
		// 这里就不关闭了，让调用者去关闭就ok了
	}

	@Override
	public void closeClientQuietly(FTPClient ftpClient) {
		this.closeFtpClientQuietly(ftpClient);
	}
	
}
